﻿using Microsoft.Extensions.Options;
using System;
using System.Collections;
using System.Collections.Generic;
using Tsual.DataPipeline;
using System.Linq;
using System.Reflection;
using Tsual.DataPipeline.AspCore;

namespace Tsual.DataPipeline.AspCore
{
    public class DataPipelineProviderOptions : IOptions<DataPipelineProviderOptions>
    {
        public DataPipelineProviderOptions Value => this;
    }

    public class ConfigurableMouldOption<TMould> : IOptions<ConfigurableMouldOption<TMould>>
        where TMould : AutoConfigurableMould<TMould>
    {
        public ConfigurableMouldOption<TMould> Value => this;

        public Action<AutoConfigurableMould<TMould>, DataPipelineProvider> ConfigurationAction { get; set; }
    }


    public sealed class DataPipelineProvider
    {
        public IServiceProvider ServiceProvider { get; }
        DataPipelineProviderOptions options;
        Func<Type, object> ServiceResolveFunction;

        public DataPipelineProvider(IServiceProvider serviceProvider, IOptions<DataPipelineProviderOptions> options)
        {
            this.ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
            this.options = (options ?? throw new ArgumentNullException(nameof(options))).Value;
            ServiceResolveFunction = t => serviceProvider.GetService(t);
        }

        public DataPipeline<TData, TMeta> Create<TData, TMeta>(params Type[] pipeTypes)
        {
            if (pipeTypes is null)
            {
                throw new ArgumentNullException(nameof(pipeTypes));
            }

            var pipeLine = new DataPipeline<TData, TMeta>(ServiceResolveFunction);
            foreach (var t in pipeTypes)
                typeof(DataPipeline<TData, TMeta>).GetMethod("AddPipe").MakeGenericMethod(t).Invoke(pipeLine, new object[0]);
            return pipeLine;
        }

        public object Create(Type data, Type meta, Type[] pipeTypes)
        {
            if (data is null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            if (meta is null)
            {
                throw new ArgumentNullException(nameof(meta));
            }

            var type = typeof(DataPipeline<,>).MakeGenericType(pipeTypes[0], pipeTypes[1]);
            var pipeLine = type.GetConstructor(new Type[] { typeof(Func<Type, object>) }).Invoke(new object[] { ServiceResolveFunction });
            if (!(pipeTypes is null) && pipeTypes.Length > 0)
            {
                var method = type.GetMethod("AddPipe");
                for (int i = 0; i < pipeTypes.Length; i++)
                {
                    method.MakeGenericMethod(pipeTypes[i]).Invoke(pipeLine, new object[0]);
                }
            }

            return pipeLine;
        }

        public object Create(Type inheritType)
        {
            if (inheritType is null || !new InheritEnumable(inheritType).Any(pt => pt.Name == typeof(DataPipeline<,>).Name))
            {
                throw new ArgumentNullException(nameof(inheritType));
            }

            var ctor = inheritType.GetConstructor(new Type[] { typeof(Func<Type, object>) });
            if (ctor == null) return null;

            return ctor.Invoke(new object[] { ServiceResolveFunction });
        }
    }

    public abstract class AutoConfigurableMould<TMould>
        where TMould : AutoConfigurableMould<TMould>
    {
        private IOptions<ConfigurableMouldOption<TMould>> option;
        protected DataPipelineProvider provider;

        protected AutoConfigurableMould(IOptions<ConfigurableMouldOption<TMould>> option, DataPipelineProvider provider)
        {
            this.option = option ?? throw new ArgumentNullException(nameof(option));
            this.provider = provider ?? throw new ArgumentNullException(nameof(provider));
            option.Value.ConfigurationAction?.Invoke(this, provider);
        }

    }
}

namespace Microsoft.Extensions.DependencyInjection
{
    public static class AspnetCoreExtensions
    {
        public static DataPipelineBuilder AddTsualDataPipeline(this IServiceCollection services)
        {
            services.AddOptions();
            services.AddSingleton<PipeObjectFactory>();
            services.AddScoped<DataPipelineProvider>();
            return new DataPipelineBuilder(services);
        }

        public static DataPipelineBuilder AddTsualDataPipeline(this IServiceCollection services, Action<DataPipelineProviderOptions> setupAction)
        {
            services.AddOptions();
            services.Configure(setupAction);
            services.AddSingleton<PipeObjectFactory>();
            services.AddSingleton<DataPipelineProvider>();
            return new DataPipelineBuilder(services);
        }

        /// <summary>
        /// configure TMould
        /// </summary>
        /// <typeparam name="TMould"></typeparam>
        /// <param name="services"></param>
        /// <param name="customSetter"></param>
        /// <returns></returns>
        public static DataPipelineBuilder ConfigureMould<TMould>(this DataPipelineBuilder services, ServiceLifetime lifetime = ServiceLifetime.Scoped, Action<TMould, DataPipelineProvider,IServiceProvider> customSetter = null)
            where TMould : AutoConfigurableMould<TMould>
        {
            Action<ConfigurableMouldOption<TMould>> setupAction = cfg =>
            {
                cfg.ConfigurationAction = (ins, prod) =>
                {
                    ins.GetType().GetProperties()
                        .Where(t => t.CanWrite)
                        .Where(t => t.GetValue(ins) == null)
                        .Where(t => new InheritEnumable(t.PropertyType).Any(pt => pt.Name == typeof(DataPipeline<,>).Name))
                        .ToList()
                        .ForEach(t =>
                        {
                            t.SetValue(ins, prod.Create(t.PropertyType));
                        });
                    customSetter?.Invoke((TMould)ins, prod, prod.ServiceProvider);
                };
            };
            services.AddOptions<ConfigurableMouldOption<TMould>>();
            services.Configure(setupAction);
            services.Add(new ServiceDescriptor(typeof(TMould), typeof(TMould), lifetime));
            services.AddScoped<TMould>();
            return services;
        }

    }


    public class DataPipelineBuilder : IServiceCollection
    {
        public ServiceDescriptor this[int index] { get => Services[index]; set => Services[index] = value; }

        public DataPipelineBuilder(IServiceCollection services)
        {
            Services = services ?? throw new ArgumentNullException(nameof(services));
        }

        public IServiceCollection Services { get; }

        public int Count => Services.Count;

        public bool IsReadOnly => Services.IsReadOnly;

        public void Add(ServiceDescriptor item)
        {
            Services.Add(item);
        }

        public void Clear()
        {
            Services.Clear();
        }

        public bool Contains(ServiceDescriptor item)
        {
            return Services.Contains(item);
        }

        public void CopyTo(ServiceDescriptor[] array, int arrayIndex)
        {
            Services.CopyTo(array, arrayIndex);
        }

        public IEnumerator<ServiceDescriptor> GetEnumerator()
        {
            return Services.GetEnumerator();
        }

        public int IndexOf(ServiceDescriptor item)
        {
            return Services.IndexOf(item);
        }

        public void Insert(int index, ServiceDescriptor item)
        {
            Services.Insert(index, item);
        }

        public bool Remove(ServiceDescriptor item)
        {
            return Services.Remove(item);
        }

        public void RemoveAt(int index)
        {
            Services.RemoveAt(index);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return Services.GetEnumerator();
        }
    }
}

namespace System.Reflection
{
    public class InheritEnumable : IEnumerable<Type>
    {
        private Type target;

        public InheritEnumable(Type target)
        {
            this.target = target ?? throw new ArgumentNullException(nameof(target));
        }

        public IEnumerator<Type> GetEnumerator() => new InnerEnumerator(target);

        IEnumerator IEnumerable.GetEnumerator() => new InnerEnumerator(target);

        public class InnerEnumerator : IEnumerator<Type>
        {
            private Type target;

            public InnerEnumerator(Type target)
            {
                this.target = target ?? throw new ArgumentNullException(nameof(target));
            }

            public Type Current { get; private set; }

            object IEnumerator.Current => Current;

            public void Dispose()
            {

            }

            public bool MoveNext()
            {
                if (Current == null)
                {
                    Current = target;
                    return true;
                }
                else if (Current.BaseType == null) return false;
                else
                {
                    Current = Current.BaseType;
                    return true;
                }
            }

            public void Reset()
            {
                Current = null;
            }
        }
    }

}